package com.cyou.nad.dao.mybatis;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.sql.DataSource;

import org.apache.ibatis.builder.xml.dynamic.ForEachSqlNode;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.MappedStatement.Builder;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.SqlSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.Assert;

import com.cyou.nad.datastore.query.dialect.Dialect;
import com.cyou.nad.datastore.query.dialect.DialectFactory;

/**
 * ClassName:PageInterceptor <br/>
 * Function:分页拦截器. <br/>
 * date:2012-8-8 下午2:31:45 <br/>
 *
 * @author chenkaihong
 * @update lzm
 * @version
 */
@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
		RowBounds.class, ResultHandler.class }) })
public class PageInterceptor implements Interceptor {

	/**
	 * log:日志.
	 */
	private static final Logger LOG = LoggerFactory.getLogger(PageInterceptor.class);

	/**
	 * @param invocation 反射
	 * @return
	 * @throws Throwable Throwable
	 * @return object
	 * @see org.apache.ibatis.plugin.Interceptor#intercept(org.apache.ibatis.plugin.Invocation)
	 */
	public Object intercept(Invocation invocation) throws Throwable {
		//不分页
		if(invocation==null||invocation.getArgs()==null||invocation.getArgs().length<3){
			return invocation.proceed();
		}
		if (!(invocation.getArgs()[2] instanceof PageRowBounds)) {
			return invocation.proceed();
		}
		int totalRow = 0;
		ResultSet rs=null;
		PreparedStatement countStmt=null;
		Connection connection=null;
		try {
			MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
			Object parameter = (Object) invocation.getArgs()[1];
			PageRowBounds pageRowBounds = (PageRowBounds) invocation.getArgs()[2];
			BoundSql boundSql = mappedStatement.getBoundSql(parameter);
			Object parameterObject = boundSql.getParameterObject();

			//计算总数
			StringBuffer countSql = new StringBuffer();
			countSql.append("select count(1) from (").append(removeOrders(boundSql.getSql())).append(") c");
			DataSource dataSource = mappedStatement.getConfiguration().getEnvironment().getDataSource();
			connection = dataSource.getConnection();
			countStmt = connection.prepareStatement(countSql.toString());
			BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql.toString(),boundSql.getParameterMappings(), parameterObject);
			this.setParameters(countStmt, mappedStatement, countBS, parameterObject);
			rs = countStmt.executeQuery();
			if (rs.next()) {
				totalRow = rs.getInt(1);
			}
			if(totalRow<=0)
				return invocation.proceed();

			String newSql = getLimitSql(dataSource,pageRowBounds, boundSql.getSql());
			//设置总记录数，以便外部获取总记录数
			pageRowBounds.setTotalCount(totalRow);
			LOG.info("The sql after PageInterceptor: {}", newSql);
			invocation.getArgs()[2] = new RowBounds(RowBounds.NO_ROW_OFFSET, RowBounds.NO_ROW_LIMIT);
			BoundSql newBoundSql = new BoundSql(mappedStatement.getConfiguration(), newSql,boundSql.getParameterMappings(), boundSql.getParameterObject());
			MappedStatement newMs = copyFromMappedStatement(mappedStatement, new BoundSqlSqlSource(newBoundSql));
			invocation.getArgs()[0] = newMs;
		} catch (Exception e) {
			e.printStackTrace();
			LOG.error("page query error",e);
		}finally{
			try {
				rs.close();
			} catch (Exception e) {
			}
			try {
				countStmt.close();
			} catch (Exception e) {
			}
			try {
				connection.close();
			} catch (Exception e) {
			}
		}
		
		return invocation.proceed();
	}

	/**
	 * @param target 目标
	 * @return 对象
	 * @see org.apache.ibatis.plugin.Interceptor#plugin(java.lang.Object)
	 */
	public Object plugin(Object target) {
		return Plugin.wrap(target, this);
	}

	/**
	 * @param arg0 arg
	 * @see org.apache.ibatis.plugin.Interceptor#setProperties(java.util.Properties)
	 */
	public void setProperties(Properties arg0) {
	}

	/**
	 *
	 * setParameters:  . <br/>
	 *
	 * @author yuanchao
	 * @param ps PreparedStatement
	 * @param mappedStatement 执行语句对象
	 * @param boundSql BoundSql对象
	 * @param parameterObject 执行语句所传入的参数对象
	 * @throws SQLException SQLException
	 */
	@SuppressWarnings("unchecked")
	private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,
			Object parameterObject) throws SQLException {
		ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
		List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
		if (parameterMappings != null) {
			Configuration configuration = mappedStatement.getConfiguration();
			TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
			MetaObject metaObject = parameterObject == null ? null : configuration.newMetaObject(parameterObject);
			for (int i = 0; i < parameterMappings.size(); i++) {
				ParameterMapping parameterMapping = parameterMappings.get(i);
				if (parameterMapping.getMode() != ParameterMode.OUT) {
					Object value;
					String propertyName = parameterMapping.getProperty();
					PropertyTokenizer prop = new PropertyTokenizer(propertyName);
					if (parameterObject == null) {
						value = null;
					} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
						value = parameterObject;
					} else if (boundSql.hasAdditionalParameter(propertyName)) {
						value = boundSql.getAdditionalParameter(propertyName);
					} else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX)
							&& boundSql.hasAdditionalParameter(prop.getName())) {
						value = boundSql.getAdditionalParameter(prop.getName());
						if (value != null) {
							value = configuration.newMetaObject(value).getValue(
									propertyName.substring(prop.getName().length()));
						}
					} else {
						value = metaObject == null ? null : metaObject.getValue(propertyName);
					}
					@SuppressWarnings("rawtypes")
					TypeHandler typeHandler = parameterMapping.getTypeHandler();
					if (typeHandler == null) {
						throw new ExecutorException("There was no TypeHandler found for parameter " + propertyName
								+ " of statement " + mappedStatement.getId());
					}
					typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());
				}
			}
		}
	}

	/**
	 *
	 * getLimitSql: 拼接limit语句 . <br/>
	 *
	 * @author yuanchao
	 * @param dataSource 
	 * @param page 封装了limit语句所需要的两个参数
	 * @param sql 要改变的sql语句
	 * @return 改变后的sql语句
	 */
	private String getLimitSql(DataSource dataSource, PageRowBounds page, String sql) {
		Dialect dialect = DialectFactory.getDialect(dataSource);
		String ret=dialect.getLimitString(sql, true);
		ret=ret.replaceFirst("\\?", page.getOffset()+"");
		ret=ret.replaceFirst("\\?", page.getLimit()+"");
		return ret;
	}
	
	/**
	 * 去掉 order 子句
	 * @param sql
	 * @return
	 */
	private String removeOrders(String sql) {
		Assert.hasText(sql);
		Pattern p = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*",
				Pattern.CASE_INSENSITIVE);
		Matcher m = p.matcher(sql);
		StringBuffer sb = new StringBuffer();
		while (m.find()) {
			m.appendReplacement(sb, "");
		}
		m.appendTail(sb);
		return sb.toString();
	}
	
	/**
	 *
	 * copyFromMappedStatement:  . <br/>
	 *
	 * @author yuanchao
	 * @param ms ms
	 * @param newSqlSource newSqlSource
	 * @return MappedStatement对象
	 */
	private MappedStatement copyFromMappedStatement(MappedStatement ms, SqlSource newSqlSource) {
		Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), newSqlSource,
				ms.getSqlCommandType());
		builder.resource(ms.getResource());
		builder.fetchSize(ms.getFetchSize());
		builder.statementType(ms.getStatementType());
		builder.keyGenerator(ms.getKeyGenerator());
		builder.keyProperty(ms.getKeyProperty());
		builder.timeout(ms.getTimeout());
		builder.parameterMap(ms.getParameterMap());
		builder.resultMaps(ms.getResultMaps());
		builder.cache(ms.getCache());
		MappedStatement newMs = builder.build();
		return newMs;
	}

	/**
	 *
	 * ClassName:BoundSqlSqlSource. <br/>
	 * Function: <br/>
	 * date:2012-8-10 上午10:30:32 <br/>
	 *
	 * @author yuanchao
	 * @version PageInterceptor
	 */
	public static class BoundSqlSqlSource implements SqlSource {

		/**
		 * boundSql.
		 */
		private BoundSql boundSql;

		/**
		 * Creates a new instance of BoundSqlSqlSource.
		 *
		 * @param boundSql BoundSql对象
		 */
		public BoundSqlSqlSource(BoundSql boundSql) {
			this.boundSql = boundSql;
		}

		/**
		 * @param parameterObject 参数对象
		 * @return BoundSql对象
		 * @see org.apache.ibatis.mapping.SqlSource#getBoundSql(java.lang.Object)
		 */
		public BoundSql getBoundSql(Object parameterObject) {
			return boundSql;
		}
	}
	
	public static void main(String[] args) {
		String sql="select * from abc where #dfsdf,#dadf,#dfgsdfg limit ?,?";
		sql=sql.replaceFirst("\\?", "1");
		sql=sql.replaceFirst("\\?", "100");
		System.out.println(sql);
		
	}
}
